#ifndef __LOGIC_FILE__
#define __LOGIC_FILE__

#include "virtfile.h"

namespace xm
{
	struct StandaloneInfo
	{
		/////////////////////////////////////////////
		xv::xvalue_t	_FileName;
		FSTATUS			_Status;
		/////////////////////////////////////////////
		StandaloneInfo() : _Status(0)
		{
		}
		HRESULT setFileName(xv::xvalue_t & rName )
		{
			if( !rName.isMBS() && !rName.isWCS() )
				return E_UNEXPECTED;
			_FileName = rName;
			return S_OK;
		}
		HRESULT getFileName(xv::AnyValue & rName )
		{
			if( !_FileName.isMBS() && !_FileName.isWCS() )
				return E_UNEXPECTED;
			rName = _FileName;
			return S_OK;
		}
		VOID close()
		{
			_Status = 0;
		}
		void clear()
		{
			close();
			_FileName.clear();
		}
		FSTATUS mod_status( FSTATUS add, FSTATUS del )
		{
			if( del ) _Status &= (~del);
			if( add ) _Status |= add;
			return _Status;
		}
	};

	class MemoryFile : public IKFileStream, public IKMemoryBlock, UnkBase
	{
	protected:

		StandaloneInfo	_alone;
		LPVOID			_buffer;
		SIZE_T			_bytes;
		SIZE_T			_cursor;
		ACCESSMASK		_acc;
		BOOL			_from_attach;

	public:

		UNKNOWN_IMP3_(IKFileStream,IKBStream,IKMemoryBlock);

		DEFAULT_INIT_CLASS();

		MemoryFile() : _acc(F_READ),_buffer(0), _bytes(0), _cursor(0),_from_attach(0)
		{
		}
		~MemoryFile()
		{
			Close();
		}
		STDMETHOD_(BOOL,IsVirtual)()
		{
			return TRUE;
		}
		STDMETHOD(GetFileName)( xv::AnyValue& fileName )
		{
			return _alone.getFileName(fileName);
		}
		STDMETHOD_(ACCESSMASK,GetAccess)()
		{
			return _acc;
		}
		STDMETHOD(SetAccess)( ACCESSMASK uAccess )
		{
			_acc = uAccess;
			return S_OK;
		}
		STDMETHOD_(FSTATUS,GetStatus)()
		{
			return _alone._Status;
		}
		STDMETHOD(ModifyStatus)( FSTATUS uAdd, FSTATUS uRemove = 0 )
		{
			_alone.mod_status( uAdd, uRemove );
			return S_OK;
		}
		STDMETHOD(Remove)()
		{
			ClearData();
			_alone.mod_status(F_DELETED,0);
			return S_OK;
		}
		STDMETHOD(Close)()
		{
			ClearData();
			_alone.close();
			return S_OK;
		}
		STDMETHOD(SetName)( LPCSTR aFileName = 0, LPCWSTR wFileName = 0 )
		{
			xv::xvalue_t tmp;
			if( aFileName ) tmp = aFileName;
			else if( wFileName ) tmp = wFileName;
			else return E_INVALIDARG;
			return _alone.setFileName( tmp ); 
		}
		STDMETHOD(SetName)( xv::AnyValue & rFileName )
		{
			xv::xvalue_t tmp( rFileName );
			return _alone.setFileName( tmp );
		}
		//////////////////////////////////////////////////////////////////////////
		//
		// read / write / flush
		//
		STDMETHOD(Read)(LPVOID lpBuffer, SIZE_T cbBytesToRead, SIZE_T* lpBytesRead OPTIONAL)
		{
			if( !_buffer ) return E_UNEXPECTED;
			RASSERT( _acc & F_READ, E_ACCESSDENIED );
			if( lpBytesRead ) *lpBytesRead = 0;
			if( !cbBytesToRead ) return S_OK;
			if( _cursor >= _bytes ) return S_OK;
			if( !lpBuffer ) return E_INVALIDARG;
			SIZE_T rest = _bytes - _cursor;
			cbBytesToRead = MIN( rest, cbBytesToRead );
			memcpy( lpBuffer, (uint8_t*)_buffer + _cursor, cbBytesToRead );
			_cursor += cbBytesToRead;
			if( lpBytesRead ) *lpBytesRead = cbBytesToRead;
			return S_OK;
		}
		STDMETHOD(Write)(LPCVOID lpBuffer, SIZE_T cbBytesToWrite, SIZE_T* lpBytesWritten OPTIONAL)
		{
			if( !_buffer ) return E_UNEXPECTED;
			RASSERT( _acc & F_READ, E_ACCESSDENIED );
			if( lpBytesWritten ) *lpBytesWritten = 0;
			if( !cbBytesToWrite ) return S_OK;
			if( _cursor >= _bytes ) return S_OK;
			if( !lpBuffer ) return E_INVALIDARG;
			SIZE_T rest = _bytes - _cursor;
			cbBytesToWrite = MIN( rest, cbBytesToWrite );
			memcpy( (uint8_t*)_buffer + _cursor, lpBuffer, cbBytesToWrite );
			_cursor += cbBytesToWrite;
			if( lpBytesWritten ) *lpBytesWritten = cbBytesToWrite;
			return S_OK;
		}
		STDMETHOD(Seek)( INT64 liDistance, SEEK_FORM emFrom )
		{
			UINT64 pos = _cursor;
			
			if( emFrom == SEEK_SET )
				pos = liDistance;
			else if( emFrom == SEEK_CUR )
				pos += liDistance;
			else if( emFrom == SEEK_END )
				pos = _bytes - liDistance;
			
			if( size_t(pos) != pos ) 
				return E_FAIL;

			_cursor = (size_t)pos;
			return S_OK;
		}
		STDMETHOD(Tell)( UINT64 * puliPointer )
		{
			if( !puliPointer ) return E_INVALIDARG;
			*puliPointer = _cursor;
			return S_OK;
		}
		STDMETHOD(Readp)( UINT64 uliPos, LPVOID lpBuffer, SIZE_T cbData, SIZE_T * lpEff )
		{
			RFAILED( SetPos(uliPos) );
			return Read(  lpBuffer, cbData, lpEff );
		}
		STDMETHOD(Writep)( UINT64 uliPos, LPCVOID lpBuffer, SIZE_T cbData, SIZE_T * lpEff )
		{
			RFAILED( SetPos(uliPos) );
			return Write( lpBuffer, cbData, lpEff );
		}
		STDMETHOD(SetPos)( UINT64 uliPos )
		{
			if( size_t(uliPos) != uliPos ) 
				return E_FAIL;
			_cursor = (size_t)uliPos;
			return S_OK;
		}
		STDMETHOD_(UINT64,Length)()
		{
			return _bytes;
		}
		STDMETHOD(Truncate)()
		{
			return E_NOTIMPL;
		}

		//////////////////////////////////////////////////////////////////////////

		STDMETHOD(Alloc)( SIZE_T uBytes )
		{
			if( _buffer ) return E_UNEXPECTED;
			_buffer = new UINT8[uBytes];
			if( !_buffer ) return E_OUTOFMEMORY;
			_bytes = uBytes;
			_cursor = 0;
			_from_attach = FALSE;
			_acc = OF_READ;
			return S_OK;
		}
		STDMETHOD(Free)()
		{
			return Close();
		}
		STDMETHOD_(SIZE_T, Size)()
		{
			return _bytes;
		}
		STDMETHOD(Attach)( LPVOID lpBlock, SIZE_T uBytes )
		{
			if( _buffer ) return E_UNEXPECTED;
			if( !lpBlock ) return E_INVALIDARG;
			if( !uBytes ) return E_INVALIDARG;
			_buffer = lpBlock;
			_bytes = uBytes;
			_cursor = 0;
			_from_attach = true;
			_acc = F_READ;
			return S_OK;
		}
		STDMETHOD_(LPVOID, GetBuffer)()
		{
			return _buffer;
		}
	
	protected:

		VOID ClearData()
		{
			if( !_buffer ) return ;
			if( !_from_attach )
				delete _buffer;
			_buffer = 0;
			_bytes = 0;
			_cursor = 0;
			_from_attach = false;
			_acc = F_READ;
		}
	};

	class KVirtualFile : public IKFileStream, public IKVirtalFile, UnkBase
	{
	public:
		UNKNOWN_IMP3_(IKFileStream,IKBStream,IKVirtalFile);
	protected:
		StandaloneInfo	_alone;
		VirtualFile		_VirtFile;
		Mode			_Mode;
	public:
		DEFAULT_INIT_CLASS();
		KVirtualFile() : _Mode(MODE_STANDALONE)
		{
		}
		virtual ~KVirtualFile()
		{
			Clear();
		}
		STDMETHOD_(BOOL,IsVirtual)()
		{
			return TRUE;
		}
		STDMETHOD(GetFileName)( xv::AnyValue& fileName )
		{
			if( _Mode == IKVirtalFile::MODE_CACHEOFBDO )
			{
				xm::iptr<IKFileStream> file( _VirtFile.get_bdo() );
				if( file ) 
					return file->GetFileName( fileName );
				return _alone.getFileName(fileName);				
			}
			else if( _Mode == IKVirtalFile::MODE_STANDALONE )
			{
				return _alone.getFileName(fileName);
			}
			else
				return E_FAIL;
		}
		STDMETHOD_(ACCESSMASK,GetAccess)()
		{
			return _VirtFile.get_access();
		}
		STDMETHOD(SetAccess)( ACCESSMASK uAccess )
		{
			return( _VirtFile.set_access( uAccess ), 0 );
		}
		STDMETHOD_(FSTATUS,GetStatus)()
		{
			return _alone._Status;
		}
		STDMETHOD(ModifyStatus)( FSTATUS uAdd, FSTATUS uRemove = 0 )
		{
			_alone.mod_status( uAdd, uRemove );
			return S_OK;
		}
		STDMETHOD(Remove)()
		{
			_VirtFile.close();
			_alone.mod_status(F_DELETED,0);
			return S_OK;
		}
		STDMETHOD(Close)()
		{
			_VirtFile.close();
			_alone.close();
			return S_OK;
		}
		STDMETHOD(SetName)( LPCSTR aFileName = 0, LPCWSTR wFileName = 0 )
		{
			xv::xvalue_t tmp;
			if( aFileName ) tmp = aFileName;
			else if( wFileName ) tmp = wFileName;
			else return E_INVALIDARG;
			return _alone.setFileName( tmp ); 
		}
		STDMETHOD(SetName)( xv::AnyValue & rFileName )
		{
			xv::xvalue_t tmp( rFileName );
			return _alone.setFileName( tmp );
		}
		STDMETHOD(FlushDirty)()
		{
			if( _Mode != MODE_CACHEOFBDO )
				return E_UNEXPECTED;
			return _VirtFile.flush_to_bdo();
		}
		STDMETHOD(Open)( Mode aMode, OPENMODE aOpenMode, IUnknown* bdo = 0, LPREGION64 aRegion = 0 )
		{
			RFAILED( Clear() );

			iptr<IKBStream> aBackStream( bdo );
			if( !aBackStream && !bdo ) 
				return E_NOINTERFACE;
			if( aMode == MODE_CACHEOFBDO && !aBackStream )
				return E_INVALIDARG;

			REGION64 xRegion = {};
			if( aRegion ) 
			{
				xRegion = *aRegion;
			}


			if( aBackStream )
			{
				UINT64 uFileSize = aBackStream->Length();
				if( uFileSize == LENGTH_ERROR ) return E_FAIL;
				if( !aRegion )
				{
					xRegion.Offset = 0;
					xRegion.Bytes = uFileSize;
				}
				else
				{
					UINT64 RegionEnd = aRegion->Offset + aRegion->Bytes;
					if( RegionEnd > uFileSize || RegionEnd < aRegion->Offset )
						return E_INVALIDARG;
				}
			}
			//_VirtFile.set_access( aOpenMode );
			RFAILED( _VirtFile.open( aBackStream, aOpenMode, &xRegion ) );

			if( aMode == IKVirtalFile::MODE_CACHEOFBDO )
			{
				xm::iptr<IKFileStream> file(aBackStream);
				if( file )
				{
					xv::xvalue_t fname;
					if( SUCCEEDED( file->GetFileName( fname ) ) )
					{
						_alone.setFileName( fname );
					}
				}
			}
			_Mode = aMode;
			return S_OK;
		}
		STDMETHOD(GetBackStream)( IKBStream** ppstm, LPREGION64 aRegion )
		{
			RASSERT( ppstm, E_INVALIDARG );
			xm::iptr<IKBStream> stm = _VirtFile.get_bdo( aRegion );
			if( !stm ) return E_UNEXPECTED;
			if( ppstm ) *ppstm = stm.detach();
			return S_OK;
		}
		STDMETHOD(Rollback)()
		{
			//if( _Mode != MODE_CACHEOFBDO ) 
			//	return E_UNEXPECTED;
			RFAILED( _VirtFile.rollback() );
			_alone.mod_status( 0, F_MODIFIED );
			return S_OK;
		}
		STDMETHOD_(VOID*,LockPage)( UINT64 Offset )
		{
			return NULL;
		}
		STDMETHOD(UnlockPage)( UINT64 Offset )
		{
			return E_NOTIMPL;
		}
		//////////////////////////////////////////////////////////////////////////
		//
		// read / write / flush
		//
		STDMETHOD(Read)(LPVOID lpBuffer, SIZE_T cbBytesToRead, SIZE_T* lpBytesRead OPTIONAL)
		{
			return _VirtFile.read( lpBuffer, cbBytesToRead, lpBytesRead );
		}
		STDMETHOD(Write)(LPCVOID lpBuffer, SIZE_T cbBytesToWrite, SIZE_T* lpBytesWritten OPTIONAL)
		{
			SIZE_T cbEff = 0;
			RFAILED( _VirtFile.write( lpBuffer, cbBytesToWrite, &cbEff ) );
			if( cbEff ) _alone.mod_status(F_MODIFIED, 0);
			if( lpBytesWritten ) *lpBytesWritten = cbEff;
			return S_OK;
		}
		STDMETHOD(Seek)( INT64 liDistance, SEEK_FORM emFrom )
		{
			return _VirtFile.setptr( liDistance, emFrom );
		}
		STDMETHOD(Tell)( UINT64 * puliPointer )
		{
			if( !puliPointer ) return E_INVALIDARG;
			*puliPointer = _VirtFile.tell();
			return S_OK;
		}
		STDMETHOD(Readp)( UINT64 uliPos, LPVOID lpBuffer, SIZE_T cbData, SIZE_T * lpEff )
		{
			return _VirtFile.readp( uliPos, lpBuffer, cbData, lpEff );
		}
		STDMETHOD(Writep)( UINT64 uliPos, LPCVOID lpBuffer, SIZE_T cbData, SIZE_T * lpEff )
		{
			SIZE_T tmp = 0;
			RFAILED( _VirtFile.writep( uliPos, lpBuffer, cbData, &tmp ) );
			if( lpEff ) *lpEff = tmp;
			if( tmp ) _alone.mod_status(F_MODIFIED,0);
			return S_OK;
		}
		STDMETHOD(SetPos)( UINT64 uliPos )
		{
			return _VirtFile.setptr( uliPos, SEEK_SET );
		}
		STDMETHOD_(UINT64,Length)()
		{
			return _VirtFile.length();
		}
		STDMETHOD(Truncate)()
		{
			return _VirtFile.truncate();
		}


	protected:

		HRESULT Clear()
		{
			_VirtFile.close();
			_alone.clear();
			return S_OK;
		}
	};


};
#endif